-
Couldn't load subscription status.
- Fork 6.1k
8314323: Implement JEP 527: TLS 1.3 Hybrid Key Exchange #27614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
👋 Welcome back hchao! A progress list of the required criteria for merging this PR into |
|
❗ This change is not yet ready to be integrated. |
|
@haimaychao The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
|
/contributor add @jnimeh |
|
/contributor add @wangweij |
|
@haimaychao |
|
@haimaychao |
Webrevs
|
| algParams.init(keAlgParamSpec); | ||
| // Skip AlgorithmParameters for KEMs (not supported) | ||
| if (namedGroupSpec == NamedGroupSpec.NAMED_GROUP_KEM) { | ||
| if (defaultProviderName == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We assume that if provider is not null then it must be DH without doing any checks to confirm that. It would be cleaner to call getProvider() instead.
Provider p = getProvider();
if (p == null) {
KeyFactory.getInstance(name);
} else {
KeyFactory.getInstance(name, p);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's actually unchanged, do you plan to do it in the next iteration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry. Changed to call getProvider() as suggested.
|
|
||
| private static KEM getKEM(String name) throws NoSuchAlgorithmException { | ||
| if (name.startsWith("secp") || name.equals("X25519") || | ||
| name.equals("X448")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need X448 here? Also, please provide a comment for this method with functionality description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
X448 removed, and comment added.
| X25519(32, 32, | ||
| "XDH", "XDH", NamedParameterSpec.X25519), | ||
|
|
||
| X448(56, 56, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need X448 and P521?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need, no. Want, yes. The support for traditional curves that are not part of the first round of hybrid KEMs lays the groundwork for future hybrid KEMs that might use these larger curves. It also gives us the base framework to move these algorithms as named groups to KEM implementations in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks for the explanation! I guess it makes sense if we expect those curves to be used in the future rounds of hybrid KEM.
| implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { | ||
| private final PublicKey pkR; | ||
| private final PrivateKey skR; | ||
| private final AlgorithmParameterSpec spec; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable is not being used, it can be removed together with the constructor parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed variable spec.
| } | ||
| } else if (k instanceof XECKey xkey | ||
| && xkey.getParams() instanceof NamedParameterSpec ns) { | ||
| if (ns.getName().equalsIgnoreCase("X25519")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use NamedParameterSpec.X25519?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
| ProtocolVersion.PROTOCOLS_TO_13, | ||
| PredefinedDHParameterSpecs.ffdheParams.get(8192)), | ||
|
|
||
| ML_KEM_512(0x0200, "MLKEM512", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are they needed for this Jep?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We added ML-KEM NamedGroups with null AlgorithmParameterSpec, and they won’t appear as negotiable named groups. They were added to support debug display and recognition of MLKEM named groups when used in the key share, so we can see them in debug and know if they are used. It'd help for interop debugging/testing.
| // Finite Field Groups (XDH) | ||
| NAMED_GROUP_XDH("XDH", XDHScheme.instance), | ||
|
|
||
| NAMED_GROUP_KEM("PQC", KEMScheme.instance), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That Choice of Name needs probably an explaining comment if it is for pure PQC and/ormhybrid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can rename "PQC" to "KEM" to be consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename done.
| } else { // default groups | ||
| NamedGroup[] groups = new NamedGroup[] { | ||
|
|
||
| // Hybrid key agreements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like all the infra for X448MLKEM1024 is there, so rather than removing x448 from this patch, why not implement it (it’s more obvious than P511 Variants)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed the infrastructure is there, but I don't see an IETF draft that covers that hybrid variant for TLS, nor do I see an IANA mapping for it here: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
There needs to be a standard for TLS 1.3 backing these hybrid KEMs before we implement them.
| FFDHE_2048, | ||
| FFDHE_3072, | ||
| FFDHE_4096, | ||
| FFDHE_6144, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the choise to knock out ffdhe6144 and 8192 from the default list was done on purpose. I don't think they get much use and they can always be re-enabled via SSLParameters or the system property. We're open to feedback on this if you or others feel like they should remain in place, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change is I think ok, doesn’t make much of a difference for most cases I was just thinking it needed its own commit and ticket reference but if it was intentional fine as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you make a fair point here. It probably deserves its own change, JBS entry, CSR, etc. We'll leave them in for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ffdhe6144 and 8192 added.
| } | ||
|
|
||
| private static AlgorithmParameterSpec getSpec(String name) { | ||
| if (name.startsWith("secp")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be repeated multiple times including the case sensitive string, maybe have an APS.isGenericEC() helper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
|
||
| private static int leftPublicLength(String name) { | ||
| return switch (name.toLowerCase(Locale.ROOT)) { | ||
| case "secp256r1" -> 65; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn’t that Part formte named groups, maybe Look it up instead?
|
|
||
| private static byte[] concat(byte[]... inputs) { | ||
| ByteArrayOutputStream o = new ByteArrayOutputStream(); | ||
| Arrays.stream(inputs).forEach(o::writeBytes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want a non presized buffer and a stream in the handshake hot path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to use presized buffer.
| * @run main/othervm -Djdk.tls.namedGroups="secp384r1" | ||
| DisabledCurve DISABLE_NONE PASS | ||
| * @run main/othervm -Djdk.tls.namedGroups="secp384r1" | ||
| DisabledCurve secp384r1 FAIL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test case fails whether we specify -Djdk.tls.namedGroups="secp384r1" or not, because the test certificate also uses secp384r1 algorithm in the signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an exsiting test entry, and perahps filing a separate ticket if any change shall be made.
|
Can you add these new groups to |
|
New groups added to test/jdk/javax/net/ssl/TLSCommon/NamedGroup.java. |
|
/csr |
|
@haimaychao an approved CSR request is already required for this pull request. |
| FFDHE6144("ffdhe6144"), | ||
| FFDHE8192("ffdhe8192"); | ||
|
|
||
| X25519MLKEM768("X25519MLKEM768); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid syntax: The matching quotation mark is missing,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
| FFDHE8192("ffdhe8192"); | ||
|
|
||
| X25519MLKEM768("X25519MLKEM768); | ||
| SecP256r1MLKEM768("SecP256r1MLKEM768"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid syntax: enums should be comma-separated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
| NamedParameterSpec.X25519MLKEM768, | ||
| "DH"), | ||
|
|
||
| SecP256r1MLKEM768(0x11eb, "SecP256r1MLKEM768", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: the rest of named groups in this file are all upper-cased.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed hybrid groups to upper-case.
| // derives the secret after receiving the client's share (KA). | ||
| // However, this is changed for KEM: the server (as encapsulator) | ||
| // must perform both actions — derive the secret and generate the | ||
| // encapsulated message at the same time during SHKeyShareProducer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: during SHKeyShareProducer doesn't seem gramatically correct, did you mean during encapsulation in SHKeyShareProducer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, comment updated.
| // Traditional Key Agreement (KA): | ||
| // Both peers perform similar operations: generate a public key, | ||
| // send it, and compute a shared secret upon receiving the peer's | ||
| // public key. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: We use both public key and key share in this comment when describing Traditional Key Agreement. I think we should use only key share (as in RFC 8446) when describing a traditional key exchange to avoid any confusion with KEM's public key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed the comment.
| * | ||
| * @since 26 | ||
| */ | ||
| public static final NamedParameterSpec SecP384r1MLKEM1024 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Let's make these all upper-case as well to keep things consistent across files.
|
|
||
| X25519MLKEM768("X25519MLKEM768"), | ||
| SecP256r1MLKEM768("SecP256r1MLKEM768"), | ||
| SecP384r1MLKEM1024("SecP384r1MLKEM1024"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Make those uppercase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We keep the actual name strings the same as in spec, the enum variables are to be uppercase for consistency.
| } | ||
| } | ||
|
|
||
| throw new InvalidKeySpecException(keySpec.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check null keySpec, and throw new InvalidKeySpecException("keySpec must not be null").
For non-null keySpec, I may use:
throw new InvalidKeySpecException(keySpec.getClass().getName() + " not supported.");
| public record SecretKeyImpl(SecretKey k1, SecretKey k2) implements SecretKey { | ||
| @Override | ||
| public String getAlgorithm() { | ||
| return "Hybrid"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this name be added to CSR and Java Security Standard Algorithm Names doc?
| "ED25519", "ED448", "X25519", "X448", | ||
| "ML_DSA_44", "ML_DSA_65", "ML_DSA_87", | ||
| "ML_KEM_512", "ML_KEM_768", "ML_KEM_1024", | ||
| "X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it sound like an option to use the similar naming style like "ML_KEM_512" for the new names? for example, X25519MLKEM768 -> X25519_ML_KEM_768
| */ | ||
| private static KEM getKEM(String name) throws NoSuchAlgorithmException { | ||
| if (APS.isGenericEC(name) || APS.isXDH(name)) { | ||
| return KEM.getInstance("DH", DH.PROVIDER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May I get more information about the hard-coded provider DH.PROVIDER? Could it be more general so that other KEM provider can also be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the DH provider is there give a KEM-like face on the NIST curves, x25519/448, etc., mainly for the purposes of assisting the work done within the hybrid KEM. The underlying work being done by the internal DH provider still ends up doing KeyAgreement, KeyPairGenerator and KeyFactory operations, and those should go through the usual provider selection process.
| // DH in its own private provider so we always getInstance from here. | ||
| public static final Provider PROVIDER = new ProviderImpl(); | ||
|
|
||
| private static class ProviderImpl extends Provider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not expose the Provider to public?
| protected PublicKey engineGeneratePublic(KeySpec keySpec) | ||
| throws InvalidKeySpecException { | ||
| if (keySpec instanceof RawKeySpec rks) { | ||
| byte[] key = rks.getKeyArr(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null check on key may be missed. If the API expose to public in the future, the key length checking may be good before Arrays.copyOfRange.
| } catch (Exception e) { | ||
| leftKey = left.generatePublic(new X509EncodedKeySpec( | ||
| leftKeyBytes)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fall-back is not good for performance. Does it have to support two key spec?
| left.initialize(leftSpec, random); | ||
| right.initialize(rightSpec, random); | ||
| initialized = true; | ||
| } catch (Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be nice if not wrap InvalidParameterException twice.
| if (!initialized) { | ||
| try { | ||
| left.initialize(leftSpec); | ||
| right.initialize(rightSpec); | ||
| initialized = true; | ||
| } catch (Exception e) { | ||
| throw new ProviderException(e); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initialization block could be saved if call initialize in the constructor. See sun/security/ec/ECKeyPairGenerator.java
Implement hybrid key exchange support for TLS 1.3 by adding three post-quantum hybrid named groups: X25519MLKEM768, SecP256r1MLKEM768, and SecP384r1MLKEM1024.
Please see JEP 527 for details about this change.
Progress
Issues
Contributors
<[email protected]><[email protected]>Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/27614/head:pull/27614$ git checkout pull/27614Update a local copy of the PR:
$ git checkout pull/27614$ git pull https://git.openjdk.org/jdk.git pull/27614/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 27614View PR using the GUI difftool:
$ git pr show -t 27614Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/27614.diff
Using Webrev
Link to Webrev Comment